﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows.Forms;

namespace ImvuCacheViewer {
    public partial class Form1 : Form {
        private readonly byte[] exifBytes = new byte[] {0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0xE8, 0x45, 0x78, 0x69, 0x66};
        private readonly byte[] gifBytes = new byte[] {0x47, 0x49, 0x46, 0x38, 0x39};
        private readonly byte[] jpegBytes = new byte[] {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46};
        private readonly byte[] pngBytes = new byte[] {0x89, 0x50, 0x4E, 0x47};
        private Dictionary<string, CVEntry> iconCache = new Dictionary<string, CVEntry>();

        public Form1() {
            InitializeComponent();
        }

        public static string ProgCachePath {
            get {
                string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                                           "ImvuCacheViewer");
                if (!Directory.Exists(path)) {
                    Directory.CreateDirectory(path);
                }
                return path;
            }
        }


        private void LoadIconCache() {
            iconCache = new Dictionary<string, CVEntry>();
            string filename = Path.Combine(ProgCachePath, "program.cache");
            if (File.Exists(filename)) {
                using (StreamReader sr = File.OpenText(filename)) {
                    string line;
                    while ((line = sr.ReadLine()) != null) {
                        var entry = new CVEntry(line);
                        iconCache[entry.Filename] = entry;
                    }
                }
            }
        }

        private void FlushIconCache() {
            string filename = Path.Combine(ProgCachePath, "program.cache");
            using (StreamWriter sw = File.CreateText(filename)) {
                foreach (var cvEntry in iconCache) {
                    string line = cvEntry.Value.ToString();
                    sw.WriteLine(line);
                }
            }
            iconCache = new Dictionary<string, CVEntry>(); // clear the contents
        }

        private void RefreshEntries() {
            btnRefresh.Enabled = false;
            lvwSkins.Items.Clear();
            lvwSkins.Items.Add("Importing...");
            lvwSkins.Refresh();
            lblStatus.Text = "Working...";
            lblStatus.Refresh();

            string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "IMVU");
            if (!Directory.Exists(path)) {
                MessageBox.Show("IMVU is not installed on this computer", "Fatal Error", MessageBoxButtons.OK,
                                MessageBoxIcon.Stop);
                Close();
            }


            LoadIconCache();
            LoadProductCache();
            lvwSkins.Items.Clear();
            lvwSkins.Refresh();

            var sf = new StatusForm();


            string cacheFolderName = Path.Combine(path, "HttpCache");
            string[] cacheFileNames = Directory.GetFiles(cacheFolderName);
            sf.Count = cacheFileNames.Length;
            sf.Number = 0;
            sf.Show(this);
            sf.Refresh();
            int num = 0;

            var entries = new List<IconEntry>();
            foreach (string cacheFilePath in cacheFileNames) {
                num++;
                if (num % 20 == 0) {
                    sf.Number = num;
                }
                if (cacheFilePath == null) continue;
                if (!File.Exists(cacheFilePath)) continue;
                string fileName = Path.GetFileName(cacheFilePath);
                if (fileName == null) continue;
                var fileInfo = new FileInfo(cacheFilePath);
                if (!fileName.StartsWith("product")) {
                    if (iconCache.ContainsKey(fileName)) {
                        // skip opening file, use progcache version.
                        entries.Add(new IconEntry {
                                                       Path = cacheFilePath,
                                                       FileDate = iconCache[fileName].Date,
                                                       Type = iconCache[fileName].Type,
                                                       Size = iconCache[fileName].Size
                                                   });
                    } else {
                        ImageType imageType = GetImageTypeFromFileName(cacheFilePath);
                        if (imageType != ImageType.None) {
                            using (Image image = Image.FromFile(cacheFilePath)) {
                                entries.Add(new IconEntry {
                                                               Path = cacheFilePath,
                                                               FileDate = fileInfo.LastWriteTime,
                                                               Type = imageType,
                                                               Size = image.Size
                                                           });
                            }
                        }
                    }
                } else if (!bwProduct.IsBusy) {
                    string[] split = fileName.Split('_');
                    string productID = split[0].Replace("product", string.Empty);
                    if (productCache.ContainsKey(productID)) {
                        productInfos[productID] = productCache[productID];
                    } else {
                        var product = new ProductInfo {ID = productID, Date = fileInfo.LastWriteTime};
                        productInfos.Add(productID, product);
                        SaveProductInfo(product);
                    }
                }
            }
            sf.Close();


            entries.Sort(
                (e1, e2) => (int) (e2.FileDate - e1.FileDate).TotalMilliseconds
                );

            bwSkins.RunWorkerAsync(entries);

            if (!bwProduct.IsBusy) {
                InitLoadProducts();
                bwProduct.RunWorkerAsync();
            }
        }

        private void Form1_Load(object sender, EventArgs e) {
            RefreshEntries();
            lvwSkins.Focus();
        }

        private ImageType GetImageTypeFromFileName(string filename) {
            var buffer = new byte[10];
            bool isJpeg = true;
            bool isPng = true;
            bool isExif = true;
            bool isGif = true;
            try {
                using (FileStream reader = File.OpenRead(filename)) {
                    reader.Read(buffer, 0, buffer.Length);

                    for (int i = 0; i < jpegBytes.Length; i++) {
                        isJpeg &= (buffer[i] == jpegBytes[i]);
                        if (i < 4 || i > 5) {
                            isExif &= (buffer[i] == exifBytes[i]);
                        }
                    }

                    for (int i = 0; i < pngBytes.Length; i++) {
                        isPng &= (buffer[i] == pngBytes[i]);
                    }

                    for (int i = 0; i < gifBytes.Length; i++) {
                        isGif &= (buffer[i] == gifBytes[i]);
                    }
                }

                string fno = Path.GetFileName(filename);
                if (isJpeg || isExif) return ImageType.Jpg;
                if (isPng) return ImageType.Png;
                if (isGif) return ImageType.Gif;
                return ImageType.None;
            } catch (Exception) {
                return ImageType.None;
            }
        }

        private void OnSelectedIconChanged(object sender, EventArgs e) {
            if (picSkin.Image != null) {
                picSkin.Image.Dispose();
                picSkin.Image = null;
            }
            if (lvwSkins.SelectedItems.Count == 0) return;
            var lvi = lvwSkins.SelectedItems[0] as IconEntryListViewItem;
            if (lvi == null) return;
            if (File.Exists(lvi.IconEntry.Path)) {
                picSkin.Image = Image.FromFile(lvi.IconEntry.Path);
            } else {
                picSkin.Image = null;
                MessageBox.Show("Cache file no longer exists", "File Not Found", MessageBoxButtons.OK,
                                MessageBoxIcon.Asterisk);
            }
        }

        private void OnChangeIconView(object sender, EventArgs e) {
            lvwSkins.View = lvwSkins.View == View.Details ? View.LargeIcon : View.Details;
            btnChangeIconView.Text = btnChangeIconView.Text == "List" ? "Thumbs" : "List";
            lvwSkins.Focus();
        }

        private void CreateIconsAsync(object sender, DoWorkEventArgs e) {
            var entries = e.Argument as List<IconEntry>;
            var queue = new Queue<QueueItem>();
            if (entries == null) return;
            int numProcessed = 0;

            MethodInvoker addImages = delegate {
                while (queue.Count > 0) {
                    QueueItem dqItem = queue.Dequeue();
/*
                    imageList1.Images.Add(dqItem.Key, dqItem.LargeImage);
                    iconList.Images.Add(dqItem.Key, dqItem.SmallImage);
*/
                    int i = numProcessed * 75 / entries.Count;
                    lblStatus.Text = "Working..." + i + "%";
                    lblStatus.Refresh();
                }
            };

            MethodInvoker addListItems = delegate {
                while (queue.Count > 0) {
                    QueueItem dqItem = queue.Dequeue();
                    lvwSkins.BeginUpdate();
                    lvwSkins.Items.Add(dqItem.Item);
                    int i = 75 + numProcessed * 25 / entries.Count;
                    lblStatus.Text = "Working..." + i + "%";
                    lblStatus.Refresh();
                    lvwSkins.EndUpdate();
                }
            };

            foreach (IconEntry entry in entries) {
                string fileName = Path.GetFileName(entry.Path);
                if (fileName == null) continue;
                var queueItem = new QueueItem();
                if (iconCache.ContainsKey(fileName)) {
                    queueItem.SmallImage = iconCache[fileName].SmallImage;
                    queueItem.LargeImage = iconCache[fileName].LargeImage;
                } else {
                    using (Image img = Image.FromFile(entry.Path)) {
                        queueItem.SmallImage = imgList16.Images[0] as Bitmap;
                        queueItem.LargeImage = imgList64.Images[0] as Bitmap;
                    }

                    iconCache[fileName] = new CVEntry {
                                                             Filename = fileName,
                                                             Date = entry.FileDate,
                                                             Size = entry.Size,
                                                             Type = entry.Type,
                                                             SmallImage = queueItem.SmallImage,
                                                             LargeImage = queueItem.LargeImage
                                                         };
                }
                queueItem.Key = fileName;
                queue.Enqueue(queueItem);
                numProcessed++;

                if (queue.Count >= 25) {
                    if (InvokeRequired) Invoke(addImages);
                    else addImages();
                }
            }
            if (InvokeRequired) Invoke(addImages);
            else addImages();

            numProcessed = 0;
            foreach (IconEntry entry in entries) {
                var lvi = new IconEntryListViewItem();
                var queueItem = new QueueItem();
                queueItem.Item = lvi;
                lvi.IconEntry = entry;
                string fileName = Path.GetFileName(entry.Path);
                if (fileName == null) continue;
                lvi.Text = fileName.Substring(0, 8).ToUpper() + "...";
                string dateText;
                if (DateTime.Now.Date == entry.FileDate.Date) {
                    dateText = entry.FileDate.ToString("HH:mm:ss");
                } else if (DateTime.Now.Year == entry.FileDate.Year) {
                    dateText = entry.FileDate.ToString("MMM d HH:mm");
                } else {
                    dateText = entry.FileDate.ToString("M/d/yyyy");
                }
                lvi.SubItems.Add(dateText);
                lvi.SubItems.Add(entry.Type.ToString().ToUpper() + " file");
                lvi.SubItems.Add(entry.Size.Width + " x " + entry.Size.Height);
                //lvi.ImageKey = fileName;
                lvi.ImageIndex = 0;
                lvi.ToolTipText = "Downloaded: " + entry.FileDate.ToString(CultureInfo.CurrentCulture) + "\n" +
                                  "Size: " + entry.Size.Width + " x " + entry.Size.Height + " px\n" +
                                  "Format: " + entry.Type.ToString().ToUpper();
                queue.Enqueue(queueItem);
                numProcessed++;
                if (queue.Count >= 100) {
                    try {
                        if (InvokeRequired) Invoke(addListItems);
                        else addListItems();
                    } catch (Exception) {
                        // do nothing, we're closing.
                    }
                }
            }
            if (InvokeRequired) Invoke(addListItems);
            else addListItems();

            FlushIconCache();

            MethodInvoker mi2 = delegate {
                                    lblStatus.Text = "Ready.";
                                    btnRefresh.Enabled = true;
                                };
            if (InvokeRequired) Invoke(mi2);
            else mi2();
        }

        private void OnRefresh(object sender, EventArgs e) {
            RefreshEntries();
        }

        private void saveFileDialog1_FileOk(object sender, CancelEventArgs e) {}

        private void button3_Click(object sender, EventArgs e) {
            if (lvwSkins.SelectedItems.Count == 0) {
                MessageBox.Show("Please select an image to save", "Image Required", MessageBoxButtons.OK,
                                MessageBoxIcon.Question);
                return;
            }

            var lvi = lvwSkins.SelectedItems[0] as IconEntryListViewItem;
            if (lvi == null) return;
            switch (lvi.IconEntry.Type) {
                case ImageType.Jpg:
                    saveFileDialog1.FilterIndex = 1;
                    break;

                case ImageType.Png:
                    saveFileDialog1.FilterIndex = 2;
                    break;

                case ImageType.Gif:
                    saveFileDialog1.FilterIndex = 3;
                    break;

                default:
                    saveFileDialog1.FilterIndex = 4;
                    break;
            }

            saveFileDialog1.Title = "Save " + lvi.IconEntry.Type.ToString().ToUpper() + " file";
            saveFileDialog1.DefaultExt = lvi.IconEntry.Type.ToString().ToLower();
            saveFileDialog1.FileName = null;
            var map = new Dictionary<ImageType, ImageFormat>();
            map[ImageType.Jpg] = ImageFormat.Jpeg;
            map[ImageType.Png] = ImageFormat.Png;
            map[ImageType.Gif] = ImageFormat.Gif;

            DialogResult result = saveFileDialog1.ShowDialog();
            if (result == DialogResult.OK) {
                picSkin.Image.Save(saveFileDialog1.FileName, map[lvi.IconEntry.Type]);
            }
        }

        private void copyToolStripMenuItem_Click(object sender, EventArgs e) {
            if (lvwSkins.SelectedItems.Count == 0) {
                MessageBox.Show("Please select an item to copy");
                return;
            }

            if (picSkin.Image == null) return;
            Clipboard.SetImage(picSkin.Image);
        }

        private void bwProduct_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {}


        #region Nested type: QueueItem

        private class QueueItem {
            public IconEntryListViewItem Item { get; set; }
            public Bitmap LargeImage { get; set; }
            public Bitmap SmallImage { get; set; }
            public string Key { get; set; }
        }

        #endregion

        private void OnTabChanged(object sender, EventArgs e)
        {
            if (tabs.SelectedTab == tabSkins) {
                lvwSkins.Focus();
            }

            if (tabs.SelectedTab == tabProduct) {
                gridProducts.Focus();
            }
        }

        private void gridProducts_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == colName.Index || e.ColumnIndex == colImage.Index) {
                DataGridViewRow row = gridProducts.Rows[e.RowIndex];
                string id = row.Cells[colID.Index].Value.ToString();
                System.Diagnostics.Process.Start("http://www.imvu.com/shop/product.php?products_id=" + id);
            }
        }

        private void gridParticipantProducts_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            System.Diagnostics.Process
                .Start("http://www.imvu.com/shop/product.php?products_id=" 
                + gridParticipantProducts.Rows[e.RowIndex].Cells[colProdID.Index].Value);
        }

        private void gridProducts_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
        {
            if (e.ColumnIndex != colImage.Index) {
                e.Handled = false;
                return;
            }

            if (e.RowIndex == -1) {
                e.Handled = false;
                return;
            }

            DataGridViewRow row = gridProducts.Rows[e.RowIndex];
            string id = row.Cells[colID.Index].Value.ToString();
            ProductInfo info = productInfos[id];
            Image productImage = info.Image;
            e.Graphics.FillRectangle(Brushes.White, e.CellBounds);
            e.Graphics.DrawImage(productImage ?? DefaultImage, e.CellBounds);
            if (productImage != null) productImage.Dispose();

            e.Handled = true;
        }

        private void lvwSkins_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) {
            e.DrawDefault = true;
        }

        private void lvwSkins_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) {
            e.DrawDefault = true;
        }

        private void lvwSkins_DrawItem(object sender, DrawListViewItemEventArgs e) {

            IconEntryListViewItem item = e.Item as IconEntryListViewItem;
            if (item == null) return;
            string path = item.IconEntry.Path;
            if (lvwSkins.View == View.Details) {
                e.DrawDefault = true;
            }
            else if (lvwSkins.View == View.LargeIcon)
            {
                //e.DrawBackground();
                string id = Path.GetFileNameWithoutExtension(path);
                Rectangle bounds = e.Bounds;
                Graphics g = e.Graphics;

                Image iconImage = null;
                if (File.Exists(path)) {
                    iconImage = Image.FromFile(path);
                }
                RectangleF dstRect = new RectangleF(bounds.Left, bounds.Top, 114, 89);
                Image iconToUse = iconImage ?? imgList64.Images[0];
                GraphicsUnit pageUnit = GraphicsUnit.Pixel;
                g.DrawImage(iconToUse, dstRect, iconToUse.GetBounds(ref pageUnit), GraphicsUnit.Pixel);
                if (iconImage != null) {
                    iconImage.Dispose();
                }
                //e.DrawText();
            }
            else {
                e.DrawDefault = true;
            }

        }



    }

    public class IconEntry {
        public string Path { get; set; }
        public ImageType Type { get; set; }
        public DateTime FileDate { get; set; }
        public Size Size { get; set; }
    }

    public class IconEntryListViewItem : ListViewItem {
        public IconEntry IconEntry { get; set; }
    }

    public enum ImageType {
        None,
        Jpg,
        Gif,
        Png,
    }
}